package com.ershijin.config;

import com.ershijin.util.OAuth2ConfigurerUtils;
import com.ershijin.my.OAuth2PasswordAuthenticationConverter;
import com.ershijin.my.OAuth2PasswordAuthenticationProvider;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.UUID;

@Configuration(proxyBeanMethods = true)
public class AuthorizationServerConfig {

    /**
     * 自定义 OAuth2TokenContext 中的 OAuth 2.0 Token 属性
     */
    private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {
        JwtClaimsSet.Builder jwtClaimsSet = context.get(JwtClaimsSet.Builder.class);
        UserDetails userDetails = (UserDetails) context.getPrincipal().getPrincipal();
        jwtClaimsSet.claim("user", userDetails);
        jwtClaimsSet.claim("authorities", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray());

    };


    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        // 注入自定义 Token 对象
        http.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);

        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> tokenEndpoint.accessTokenRequestConverter(
                        new DelegatingAuthenticationConverter(Arrays.asList(
                                new OAuth2AuthorizationCodeAuthenticationConverter(),
                                new OAuth2RefreshTokenAuthenticationConverter(),
                                new OAuth2ClientCredentialsAuthenticationConverter(),
                                new OAuth2PasswordAuthenticationConverter())) // 自定义的 password 认证类型转换器
                ));


        SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();

        OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
                OAuth2ConfigurerUtils.getAuthorizationService(http),
                OAuth2ConfigurerUtils.getJwtEncoder(http),
                http.getSharedObject(AuthenticationManager.class),
                providerSettings()
        );
        oAuth2PasswordAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
        http.authenticationProvider(oAuth2PasswordAuthenticationProvider);

        return securityFilterChain;
    }


    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client")
                .clientSecret("{noop}secret")
//          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app1:8080/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .build();

        RegisteredClient registeredClient2 = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client2")
                .clientSecret("{noop}secret2")
//          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app2:8081/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app2:8081/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient, registeredClient2);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    private static RSAKey generateRsa() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .issuer("http://auth-server:9000")
                .build();
    }
}